package org.dandelion.papercheck.controller;

import org.dandelion.papercheck.dto.BaseResult;
import org.dandelion.papercheck.dto.OneDupDTO;
import org.dandelion.papercheck.dto.UploadFileResponse;
import org.dandelion.papercheck.service.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.util.*;

@SuppressWarnings("rawtypes")
@CrossOrigin
@RestController
public class UploadController {
    private static final Logger logger = LoggerFactory.getLogger(UploadController.class);
    private final StorageService storageService;
    private final PaperService paperService;
    private final DupChkService dupChkService;
    private final WeightService weightService;
    private final HistoryService historyService;

    @Autowired
    public UploadController(StorageService storageService, PaperService paperService,
                            DupChkService dupChkService, WeightService weightService,
                            HistoryService historyService) {
        this.storageService = storageService;
        this.paperService = paperService;
        this.dupChkService = dupChkService;
        this.weightService = weightService;
        this.historyService = historyService;
    }

    @PostMapping("/uploadFile")
    public BaseResult uploadFile(@RequestParam MultipartFile file, @RequestParam String username) {
        String originalFilename = file.getOriginalFilename();
        LocalDateTime dateTime = LocalDateTime.now();

        if (originalFilename == null || (!originalFilename.endsWith(".doc")
                && !originalFilename.endsWith(".docx"))) {
            return BaseResult.error(-1, "invalid file format.");
        }

        String fileName = StringUtils.cleanPath(Objects.requireNonNull(file.getOriginalFilename()));

        try {
            Path savePath = storageService.storeFile(file, fileName, username);
            fileName = savePath.getFileName().toString();
            logger.info("upload file: " + fileName);

            List<String> wordList = paperService.splitWordsOfFile(fileName);
            Map<String, Integer> weightMap = weightService.getWeightMap(wordList);
            String fingerprint = dupChkService.extractFingerprint(weightMap);

            storageService.updateFingerprint(fileName, fingerprint);

            Map<String, Double> articleSimilarMap = dupChkService.singleChk(fingerprint);

            String finalFileName = fileName;
            articleSimilarMap.entrySet().removeIf(entry -> entry.getKey().equals(finalFileName));
            Set<String> del = new HashSet<>();

            for (Map.Entry<String, Double> entry : articleSimilarMap.entrySet()) {
                if (entry.getValue() <= 3) {
                    articleSimilarMap.put(entry.getKey(), 0.99 - 0.01 * Math.pow(entry.getValue(), 3));
                } else {
                    if (0.99 - 0.01 * (Math.pow(3, 3) + 5 * (entry.getValue() - 3)) > 0.60) {
                        articleSimilarMap.put(entry.getKey(), 0.99 - 0.01 * (Math.pow(3, 3) + 5 * (entry.getValue() - 3)));
                    } else {
                        del.add(entry.getKey());
                    }
                }
            }
            for (String delFile : del) {
                articleSimilarMap.entrySet().removeIf(entry -> entry.getKey().equals(delFile));
            }

            String fileUri = ServletUriComponentsBuilder
                    .fromCurrentContextPath()
                    .path("download/")
                    .path(fileName).toUriString();

            historyService.createHistory(dateTime, username, fileName, articleSimilarMap);

            var uploadFileResponse = new UploadFileResponse(fileName, fileUri,
                    file.getContentType(), file.getSize(), articleSimilarMap);

            return BaseResult.success(uploadFileResponse);
        } catch (Exception e) {
            logger.warn(e.getMessage());
            return BaseResult.error(500, e.getMessage());
        }
    }

    @PostMapping("/uploadMultiFiles")
    public BaseResult uploadMultiFiles(@RequestParam MultipartFile[] files, @RequestParam String username) {
        List<String> fileNames = new ArrayList<>();
        List<String> figures = new ArrayList<>();

        for (MultipartFile file : files) {
            String originalFilename = file.getOriginalFilename();
            fileNames.add(file.getOriginalFilename());

            if (originalFilename == null || (!originalFilename.endsWith(".doc")
                    && !originalFilename.endsWith(".docx"))) {
                return BaseResult.error(-1, "invalid file format.");
            }

            String fileName = StringUtils.cleanPath(Objects.requireNonNull(file.getOriginalFilename()));
            Path savePath = storageService.storeFile(file, fileName, username);
            fileName = savePath.getFileName().toString();
            logger.info("upload file: " + fileName);

            List<String> wordList;

            try {
                wordList = paperService.splitWordsOfFile(fileName);
            } catch (Exception e) {
                return BaseResult.error(500, e.getMessage());
            }

            Map<String, Integer> weightMap = weightService.getWeightMap(wordList);
            String fingerprint = dupChkService.extractFingerprint(weightMap);
            figures.add(fingerprint);
            storageService.updateFingerprint(fileName, fingerprint);
        }

        List<List<Double>> lists = dupChkService.multiChk(figures);
        List<OneDupDTO> results = new ArrayList<>();
        for (int i = 0; i < lists.size(); i++) {
            for (int j = i + 1; j < lists.get(i).size(); j++) {
                if (lists.get(i).get(j) <= 8) {
                    if (lists.get(i).get(j) <= 3) {
                        results.add(new OneDupDTO(fileNames.get(i), fileNames.get(j), 0.99 - 0.01 * Math.pow(lists.get(i).get(j), 3)));
                    } else
                        results.add(new OneDupDTO(fileNames.get(i), fileNames.get(j), 0.99 - 0.01 * (Math.pow(3, 3) + 5 * (lists.get(i).get(j) - 3))));
                }
            }
        }

        return BaseResult.success(results);
    }

    @GetMapping("/download/{fileName:.+}")
    public ResponseEntity<Resource> download(@PathVariable String fileName,
                                             HttpServletRequest request) {
        Resource resource = storageService.loadFileAsResource(fileName);

        String contentType = null;
        try {
            contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
        } catch (IOException ex) {
            logger.info("Could not determine file type");
        }

        if (contentType == null) {
            contentType = "application/octet-stream";
        }

        return ResponseEntity.ok().contentType(MediaType.parseMediaType(contentType))
                .header(HttpHeaders.CONTENT_DISPOSITION,
                        "attachment; filename=\"" + resource.getFilename() + "\"")
                .body(resource);
    }
}
